package optimizers.commons.evaluator;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.swing.SwingWorker;

import org.math.plot.utils.Array;

import common.RunStatus;
import common.Static;

import optimizers.commons.GAConstants;
import optimizers.ganeat.Individual;
import optimizers.ganeat.report.LoggerNEAT;

import fitness.Fitness;
import fitness.FitnessResult;
import fitness.MathFitness;
import fitness.MathResult;

import model.OligoSystemComplex;
import model.OligoSystemGeneral;

public class FitnessEvaluationService extends SwingWorker<Void, Void> {
	protected CompletionService<Object> cservice;
	protected ExecutorService eservice;
	protected Fitness evaluator;
	protected int numberOfThreads;
	protected int averageTime = 50;// GAConstants.maxExecutionInSeconds;
	protected RunStatus runStatus;

	LoggerNEAT logger;

	public FitnessEvaluationService(int numberOfThreads, Fitness evaluator,
			RunStatus runStatus, LoggerNEAT logger) {
		this.numberOfThreads = numberOfThreads;
		eservice = Executors.newFixedThreadPool(9);
		cservice = new ExecutorCompletionService<Object>(eservice);
		this.evaluator = evaluator;
		this.runStatus = runStatus;
		this.logger = logger;
		FileOutputStream saveFile = null;
		try {
			saveFile = new FileOutputStream("evaluator");
			ObjectOutputStream save = new ObjectOutputStream(saveFile);
			save.writeObject(evaluator);
			save.close();
			saveFile.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public FitnessResult[] evaluateFitness(OligoSystemGeneral[] oligoSystems) {
		FitnessResult[] results = new FitnessResult[oligoSystems.length];
		int taskCount = 0;
		ArrayList<Future<Object>> futures = new ArrayList<Future<Object>>();
		for (int i = 0; i < oligoSystems.length; i++) {
			futures.add(cservice.submit(new FitnessEvaluationTask(i,
					oligoSystems[i], evaluator)));
			taskCount++;
		}

		double mesureAverage = 0;
		for (int i = 0; i < taskCount; i++) {
			if (runStatus != null) {
				synchronized (runStatus.lock) {
					while (runStatus.status == RunStatus.PAUSED) {
						try {
							runStatus.lock.wait();
						} catch (InterruptedException e) {
							// treat interrupt as exit request
							break;
						}
					}
				}
				if (runStatus.status == RunStatus.STOPPED) {
					return null;
				}
			}
			try {
				Future<Object> future = cservice.poll(this.averageTime,
						TimeUnit.SECONDS);

				if (future != null) {
					FitnessEvaluationResult result = (FitnessEvaluationResult) future
							.get();
					int index = result.index;
					results[index] = result.result;
					/*
					 * MathResult mathResult = (MathResult) result.result;
					 * System.out.println(mathResult.scale); for (String key :
					 * mathResult.actuals.keySet()) { System.out.println(key +
					 * " " + mathResult.actuals.get(key) + " " +
					 * mathResult.targets.get(key)); }
					 */setProgress(i * 100 / taskCount);
					if (logger != null) {
						logger.logToMemory(i + ":" + index + ":"
								+ result.result.toString() + " "
								+ result.calculationTime + "s");
					} else {

						if (RobustnessEvaluator.defaultParams != null) {
							ArrayList<Double> seqK = oligoSystems[index]
									.getSeqK();
							for (int j = 0; j < seqK.size(); j++) {
								System.out
										.print(Static.df4.format(Math.log(seqK
												.get(j))
												/ Math.log(RobustnessEvaluator.defaultParams
														.get(j)))
												+ " ");
							}
							if (results[index] == null) {
								System.out.println(0);
							} else {
								System.out.println(Static.df4
										.format(results[index].finalResult()));
							}

						} else {
							System.out.println(i + ":" + index + ":"
									+ result.result.toString() + " "
									+ result.calculationTime + "s");
						}
					}
					mesureAverage += result.calculationTime;
				} else {
					// System.out.println("Some evaluation is too slow");
					/*
					 * List<Runnable> remains = eservice.shutdownNow();
					 * logger.logToMemory(remains.size() + " remains");
					 * taskCount = i + remains.size(); futures = new
					 * ArrayList<Future<Object>>(); while
					 * (!eservice.awaitTermination(awaitTime, TimeUnit.SECONDS))
					 * ; logger.logToMemory("Service ended perfectly"); eservice
					 * = Executors.newFixedThreadPool(numberOfThreads); cservice
					 * = new ExecutorCompletionService<Object>(eservice); for
					 * (Runnable rem : remains) {
					 * futures.add(cservice.submit(rem, evaluator)); }
					 */
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		for (int i = 0; i < results.length; i++) {
			if (results[i] == null) {
				// System.out.println(i);
				OligoSystemGeneral oligoModel = oligoSystems[i];
				OligoSystemComplex oligo = new OligoSystemComplex(
						oligoModel.nSimpleSequences, oligoModel.templates,
						oligoModel.seqK, oligoModel.inhK,
						oligoModel.seqConcentration, null,
						oligoModel.reporterConcentration, oligoModel.seqString);
				oligo.printModel(oligoModel.nSimpleSequences);
				results[i] = evaluator.minFitness(oligoSystems[i]);
			}
		}
		this.averageTime = Math.max(50,
				1 + (int) ((2 * mesureAverage) / taskCount));
		return results;
	}

	@Override
	protected Void doInBackground() throws Exception {
		// TODO Auto-generated method stub
		return null;
	}
}
